package com.chenyun.satoken.starter.config;

import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.jwt.SaJwtTemplate;
import cn.dev33.satoken.jwt.SaJwtUtil;
import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.same.SaSameUtil;
import cn.dev33.satoken.stp.StpLogic;
import cn.hutool.jwt.JWT;
import com.alibaba.fastjson2.JSON;
import com.chenyun.common.factory.YAMLPropertySourceFactory;
import com.chenyun.common.vo.AjaxResult;
import com.chenyun.common.constants.SaTokenConstant;
import com.chenyun.common.vo.ApplicationProperties;
import com.chenyun.satoken.starter.interceptor.SaMultipleAccountInterceptor;
import com.chenyun.satoken.starter.stp.StpMemberUtil;
import com.chenyun.satoken.starter.stp.StpShopUtil;
import com.chenyun.satoken.starter.util.SaUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

/**
 * 配置sa-token相关的全局过滤器和全局拦截器
 * @author: Lonni
 * @date: 2022/8/12 0012 15:55
 */
@Configuration
@Slf4j
@PropertySource(value = "classpath:config/application-sa.yml",factory = YAMLPropertySourceFactory.class)
public class SaTokenConfig implements WebMvcConfigurer {


    /**
     * 白名单配置
     */
    @Autowired
    private ApplicationProperties properties;



    /**
     * 重写Sa-Token token生成策略
     */
    @Autowired
    public void setSaJwtTemplate() {
        SaJwtUtil.setSaJwtTemplate(new SaJwtTemplate() {
            @Override
            public String generateToken(JWT jwt, String keyt) {
                String token = super.generateToken(jwt, keyt);
                //将loginType加入到token前面
                return jwt.getPayload("loginType") + SaTokenConstant.TOKEN_LOGIN_TYPE_SPLIT_PREFIX + token;

            }

            @Override
            public JWT parseToken(String token, String loginType, String keyt, boolean isCheckTimeout) {
                //截取掉loginType,拿到正确的tokenValue
                token = token.split(SaTokenConstant.TOKEN_LOGIN_TYPE_SPLIT_PREFIX)[1];
                return super.parseToken(token, loginType, keyt, isCheckTimeout);
            }

        });
    }


    /**
     * 注入jwt相关
     *
     * @return
     */
    @Bean
    public StpLogic getStpLogicJwt() {
        return new StpLogicJwtForSimple();
    }


    /**
     * 为 StpShopUtil 注入 StpLogicJwt 实现
     */
    @Autowired
    public void setShopStpLogic() {
        StpShopUtil.stpLogic = new StpLogicJwtForSimple(SaTokenConstant.SHOP_LOGIN_TYPE);
        SaManager.putStpLogic(StpShopUtil.stpLogic);
    }


    /**
     * 为 StpMemberUtil 注入 StpLogicJwt 实现
     */
    @Autowired
    public void setMemberStpLogic() {
        StpMemberUtil.stpLogic = new StpLogicJwtForSimple(SaTokenConstant.MEMBER_LOGIN_TYPE);
        SaManager.putStpLogic(StpMemberUtil.stpLogic);
    }


    /**
     * 全局拦截器
     * 注册Sa-Token的注解拦截器，打开注解式鉴权功能
     *
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册Sa-Token的路由拦截器，并排除登录接口或其他可匿名访问的接口地址 (与注解拦截器无关)
        final List<String> wilteList = properties.getSatoken().getAllIgnorePath();
        log.info("addInterceptors白名单地址:{}", JSON.toJSONString(wilteList));

        /**
         * 如果在网关中没有设置忽略路径,在子项目中设置了忽略路径,sa还是会放行白名单;
         * 但这时从网关访问还是要检查token
         * 网关处已经设置拦截路径了,在这里就不要重复设置
         */
//        registry
//                .addInterceptor(new SaRouteInterceptor((req, res, handler) -> {
//                    Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
//                    log.info("请求地址:{},请求方法:{}", req.getRequestPath(), req.getMethod());
//                    log.info("白名单地址:{}",JSON.toJSONString(wilteList));
//                    //判断是不是feign请求,如果是feign请求直接放行;
////                    final String client = req.getHeader(AuthConstant.FEIGN_HEADER_CLIENT_TYPE);
////                    if (AuthConstant.FEIGN_HEADER_CLIENT_TYPE_VALUE.equals(client)){
////                        log.debug("feign请求,不执行登录拦截");
////                        return;
////                    }
//                    // 登录验证 -- 拦截所有路由,都需要登录后才能访问;排除白名单
//                    SaRouter.match("/**")
//                            .notMatch(wilteList)
//                            .check(StpUtil::checkLogin);
//                    // 终止过滤器链
////                    SaRouter.match(wilteList).stop();
////                    handlerMethods.forEach(((requestMappingInfo, handlerMethod) -> {
////                        Set<RequestMethod> methods = requestMappingInfo.getMethodsCondition().getMethods();
////                        methods.forEach(requestMethod -> {
////                            SaRouter.match(SaHttpMethod.valueOf(requestMethod.name()))
////                                    .match(new ArrayList<>(requestMappingInfo.getPatternsCondition().getPatterns()));
////                                    .check(() -> {  //这里检查的是菜单地址权限 使用于动态权限方式
////                                        log.info("请求地址:{}", req.getMethod() + "#" + requestMappingInfo.getPatternsCondition().getPatterns().toArray()[0]);
////                                        StpUtil.checkPermission(req.getMethod() + "#" + requestMappingInfo.getPatternsCondition().getPatterns().toArray()[0]);
////                                    });
////                        });
////                    }));
//                }))
//                //在这里还可以使用.excludePathPatterns()添加不适用此拦截器的接口
//                .addPathPatterns("/**")
//                //白名单直接排除,这样就不会走SaRouter.match,减少不必要步骤
//                .excludePathPatterns(wilteList);


        //添加自定义注解拦截器,包含了sa内置的注解校验
        registry.addInterceptor(new SaMultipleAccountInterceptor(handler -> {
                 // 登录验证 -- 拦截所有路由,都需要登录后才能访问;排除白名单
                    SaRouter.match("/**")
                            .notMatch(wilteList)
                            .check(p->{
                                SaUtil.checkLogin(true);
                            })
                            //提前退出匹配链
                            .stop();
                }))
                .addPathPatterns("/**")
                .excludePathPatterns(wilteList);



    }


    /**
     * 全局过滤器
     * 这里采用全局拦截器方式;
     * 用于网关转发鉴权和内部服务调用
     * @return
     */
    @Bean
    public SaServletFilter saServletFilter() {
        return new SaServletFilter()
                // 拦截路由
                .addInclude("/**")
                //放行路由
                .addExclude()
                .setAuth((auth) -> {
                    if (properties.getSatoken().getCheckInner()) {
                        log.debug("checkInner=true,需要校验网关id-token");
                        // 校验 Id-Token 身份凭证 放行 actuator
                        SaRouter.match("/**")
                                .notMatch(properties.getSatoken().getAllIgnorePath())
                                .check(SaSameUtil::checkCurrentRequestToken);
                    }
                })
                .setError(e -> {
                    String message =e.getMessage().equals("无效Same-Token：") ?"无请求权限":e.getMessage();
                    log.error("进入Sa-Token异常处理 :{}", e.getMessage());

                    //注意 这里,返回的值内部会转换为string,所以需要把返回值转换为jsonStr,否则返回的是对象字符串
                    return com.alibaba.fastjson2.JSON.toJSONString(AjaxResult.ERROR(message));
                })
                .setBeforeAuth(obj -> {
                    // ---------- 设置跨域响应头 ----------
                    SaHolder.getResponse()
                            // 允许指定域访问跨域资源
                            .setHeader("Access-Control-Allow-Origin", "*")
                            // 允许所有请求方式
                            .setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
                            // 有效时间
                            .setHeader("Access-Control-Max-Age", "3600")
                            // 允许的header参数
                            .setHeader("Access-Control-Allow-Headers", "*")
                            .setHeader("Content-Type","application/json;charset=utf-8")
                    ;

                    // 如果是预检请求，则立即返回到前端
                    SaRouter.match(SaHttpMethod.OPTIONS)
                            .free(r -> log.info("--------OPTIONS预检请求，不做处理"))
                            .back();
                });
    }

}
